namespace FIREFLYX {
    /** ubo 数组数据 类型 */
    type uboArrayDataType = new (data: ArrayLike<number> | ArrayBuffer) => any;
    /** unifrom 使用信息 */
    // eslint-disable-next-line @typescript-eslint/ban-types
    type uniformUseInfo = { lenght: number, align: number, arrType: uboArrayDataType, uploadFun: Function, defVal: any };
    /** uniform 值*/
    type uniformValue = TextureBase | number | number[] | BufferSource;
    /** uniform 值字典*/
    type uniformValueMap = { [k: string]: uniformValue | uniformValue[] };

    /** ubo 绑定 索引枚举 */
    export enum UBOBindingEnum {
        /** 相机设置 */
        CAMERA,
        /** 模型设置 */
        MODEL,
        /** 灯光设置 */
        LIGHT,
        /** 材质设置 开始 */
        MATERIAL_START,
    }

    /** 阴影级联数 */
    export enum ShadowCascade {
        /** 一个 */
        ONE = 1,
        /** 二个 */
        TWO = 2,
        /** 四个 */
        FOUR = 4
    }

    /** 引擎渲染上下文对象 */
    export class RenderContext {
        /** 相机 ubo block名 */
        public static readonly UBO_CAMERA_BLOCK_NAME = "ubo_cam_block";
        /** 模型 ubo block名 */
        public static readonly UBO_MODEL_BLOCK_NAME = "ubo_model_block";
        /** 光照 ubo block名 */
        public static readonly UBO_LIGHT_BLOCK_NAME = "ubo_light_block";

        /** 非透明渲染队列开始值 */
        public static readonly RENDER_QUEUE_OPAQUE = 0;
        /** ALPHA测试渲染队列开始值 */
        public static readonly RENDER_QUEUE_ALPHA_TEST = 2000;
        /** 透明渲染队列开始值 */
        public static readonly RENDER_QUEUE_TRANSPARENT = 4000;

        private _gl: WebGL2RenderingContext;
        private _texActivator: TextureActivator;
        private _ignoreInMaterialMap: { [k: string]: boolean } = {};
        private _ignoreInMaterialBlockMap: { [k: string]: boolean } = {};
        private _unifUseInfoMap: { [type: number]: uniformUseInfo };
        private _shadowCascades: ShadowCascade = ShadowCascade.FOUR;
        private _shadowCascadeSplitsArr: number[][] = [
            [],
            [0.333],
            [0.143, 0.428],
            [0.067, 0.2, 0.467]
        ];
        private _curCamPixelViewPort: Rect = new Rect(0, 0, 100, 100);
        private _camViewProjMatrix: Matrix = new Matrix();
        private _builtinUniformMap: Map<string, Nullable<() => (UnifromNormalValue | UnifromNormalValue[] | null)>> = new Map();

        /** 天空盒材质 */
        public skyboxMaterial: Nullable<Material> = null;
        /** 光照 周围环境关颜色 ，w分量为 hdr 强度*/
        public lightingAmbient: Color4 = new Color4(0.15, 0.22, 0.27, 0);
        /** 渲染器 容器 */
        public rendererMap: Nullable<Map<number, Renderer>>;
        /** 灯光收集器 */
        public lightCollector: LightCollector;
        /** 阴影绘制距离 */
        public shadowDistance: number = 150;
        /** 软阴影是方差阴影 */
        public isVSM : boolean = true;
        /** 方差阴影采样次数 */
        public vsmSampleCount : number = 8;
        /** 方差阴影采样范围 */
        public vsmSampleRadius : number = 3;
        /** 后渲染到纹理函数 */
        public onRenderImageFun: Nullable<(image: RenderTexture2D) => void>;

        constructor(gl: WebGL2RenderingContext) {
            this._gl = gl;
            this._texActivator = new TextureActivator(gl);
            this._unifUseInfoMap = this.makeUnifUseInfoMap();
        }

        /** 阴影级联数 范围 1-4 */
        public get shadowCascades() { return this._shadowCascades; }
        public set shadowCascades(val) {
            if (val == this._shadowCascades) return;
            val = val < 1 ? 1 : val > 4 ? 4 : val;
            this._shadowCascades = val;
        }

        /** 当前的相机 视口大小数据 */
        public get curCamPixelViewPort() { return this._curCamPixelViewPort; }
        /** 获取相机View Projection 矩阵 */
        public get camViewProjMatrix() { return this._camViewProjMatrix; }
        /** 引擎内建uniform变量 */
        public get builtinUniformMap() { return this._builtinUniformMap; }

        /**
         * 获取指定索引 阴影级联拆划分值
         * @param splitIndex 分段索引
         * @returns 有效值为 0 - 1
         */
        public getShadowCascadeSplit(splitIndex: number) {
            if (isNaN(splitIndex) || splitIndex < 0) return -1;
            const splits = this._shadowCascadeSplitsArr[this._shadowCascades - 1];
            if (splitIndex >= splits.length) return 1;
            return splits[splitIndex];
        }

        /**
         * 设置指定索引 阴影级联拆划分值
         * @param splitIndex 分段索引
         * @param val 值
         */
        public setShadowCascadeSplit(splitIndex: number, val: number) {
            if (isNaN(splitIndex) || splitIndex < 0) return;
            const splits = this._shadowCascadeSplitsArr[this._shadowCascades];
            if (splitIndex >= splits.length) return;
            const last = splitIndex == 0 || splits.length < 2 ? 0 : splits[splitIndex - 1];
            const next = splitIndex == splits.length - 1 || splits.length < 2 ? 1 : splits[splitIndex + 1];
            splits[splitIndex] = val < last ? last : val > next ? next : val;
        }

        /** webgl渲染上下文 */
        public get webgl2() { return this._gl; }
        /** webgl纹理 激活器  */
        public get textrueActivator() { return this._texActivator; }
        /** 在材质中忽略 的uniform 字典 */
        public get ignoreInMaterialMap() { return this._ignoreInMaterialMap; }
        /** 在材质中忽略 的 uniformBlock 字典 */
        public get ignoreInMaterialBlockMap() { return this._ignoreInMaterialBlockMap; }
        /** unifrom 使用信息 字典 */
        public get uniformUseInfoMap() { return this._unifUseInfoMap; }

        /** 创建uniform 类型字典 */
        private makeUnifUseInfoMap() {
            const gl = this.webgl2;
            let _map: { [type: GLenum]: uniformUseInfo } = {};

            _map[gl.FLOAT] = { lenght: 1, align: 1, arrType: Float32Array, uploadFun: gl.uniform1fv.bind(gl), defVal: 0 };
            _map[gl.FLOAT_VEC2] = { lenght: 2, align: 2, arrType: Float32Array, uploadFun: gl.uniform2fv.bind(gl), defVal: new Float32Array(2) };
            _map[gl.FLOAT_VEC3] = { lenght: 3, align: 4, arrType: Float32Array, uploadFun: gl.uniform3fv.bind(gl), defVal: new Float32Array(3) };
            _map[gl.FLOAT_VEC4] = { lenght: 4, align: 4, arrType: Float32Array, uploadFun: gl.uniform4fv.bind(gl), defVal: new Float32Array(4) };
            _map[gl.INT] = { lenght: 1, align: 1, arrType: Int32Array, uploadFun: gl.uniform1iv.bind(gl), defVal: 0 };
            _map[gl.INT_VEC2] = { lenght: 2, align: 2, arrType: Int32Array, uploadFun: gl.uniform1iv.bind(gl), defVal: new Int32Array(2) };
            _map[gl.INT_VEC3] = { lenght: 3, align: 4, arrType: Int32Array, uploadFun: gl.uniform1iv.bind(gl), defVal: new Int32Array(2) };
            _map[gl.INT_VEC4] = { lenght: 4, align: 4, arrType: Int32Array, uploadFun: gl.uniform1iv.bind(gl), defVal: new Int32Array(2) };
            _map[gl.BOOL] = { lenght: 1, align: 1, arrType: Uint32Array, uploadFun: gl.uniform1iv.bind(gl), defVal: 0 };
            _map[gl.BOOL_VEC2] = { lenght: 2, align: 2, arrType: Uint32Array, uploadFun: gl.uniform1iv.bind(gl), defVal: new Uint8Array(2) };
            _map[gl.BOOL_VEC3] = { lenght: 3, align: 4, arrType: Uint32Array, uploadFun: gl.uniform1iv.bind(gl), defVal: new Uint8Array(3) };
            _map[gl.BOOL_VEC4] = { lenght: 4, align: 4, arrType: Uint32Array, uploadFun: gl.uniform1iv.bind(gl), defVal: new Uint8Array(4) };
            _map[gl.FLOAT_MAT2] = { lenght: 8, align: 4, arrType: Float32Array, uploadFun: gl.uniformMatrix2fv.bind(gl), defVal: new Float32Array(4) };
            _map[gl.FLOAT_MAT3] = { lenght: 12, align: 4, arrType: Float32Array, uploadFun: gl.uniformMatrix3fv.bind(gl), defVal: new Float32Array(9) };
            _map[gl.FLOAT_MAT4] = { lenght: 16, align: 4, arrType: Float32Array, uploadFun: gl.uniformMatrix4fv.bind(gl), defVal: new Float32Array(16) };
            //webgl2 add
            _map[gl.UNSIGNED_INT] = { lenght: 1, align: 1, arrType: Uint32Array, uploadFun: gl.uniform1uiv.bind(gl), defVal: 0 };
            _map[gl.UNSIGNED_INT_VEC2] = { lenght: 2, align: 2, arrType: Uint32Array, uploadFun: gl.uniform2uiv.bind(gl), defVal: new Uint8Array(2) };
            _map[gl.UNSIGNED_INT_VEC3] = { lenght: 3, align: 4, arrType: Uint32Array, uploadFun: gl.uniform3uiv.bind(gl), defVal: new Uint8Array(3) };
            _map[gl.UNSIGNED_INT_VEC4] = { lenght: 4, align: 4, arrType: Uint32Array, uploadFun: gl.uniform4uiv.bind(gl), defVal: new Uint8Array(4) };
            _map[gl.FLOAT_MAT2x3] = { lenght: 8, align: 4, arrType: Float32Array, uploadFun: gl.uniformMatrix2x3fv.bind(gl), defVal: new Float32Array(6) };
            _map[gl.FLOAT_MAT2x4] = { lenght: 8, align: 4, arrType: Float32Array, uploadFun: gl.uniformMatrix2x4fv.bind(gl), defVal: new Float32Array(8) };
            _map[gl.FLOAT_MAT3x2] = { lenght: 12, align: 4, arrType: Float32Array, uploadFun: gl.uniformMatrix3x2fv.bind(gl), defVal: new Float32Array(6) };
            _map[gl.FLOAT_MAT3x4] = { lenght: 12, align: 4, arrType: Float32Array, uploadFun: gl.uniformMatrix3x4fv.bind(gl), defVal: new Float32Array(12) };
            _map[gl.FLOAT_MAT4x2] = { lenght: 16, align: 4, arrType: Float32Array, uploadFun: gl.uniformMatrix4x2fv.bind(gl), defVal: new Float32Array(8) };
            _map[gl.FLOAT_MAT4x3] = { lenght: 16, align: 4, arrType: Float32Array, uploadFun: gl.uniformMatrix4x3fv.bind(gl), defVal: new Float32Array(12) };

            return _map;
        }

        /**
         * 设置ubo 子数据
         * @param offset 偏移
         * @param val 值
         * @param arrType 数组类型
         */
        private uboSubDataSet(offset: number, val: any, arrType: uboArrayDataType) {
            const gl = this.webgl2;
            let arr: BufferSource = null as any;
            if (val != null) {
                const arrTypeSame = val.byteLength != null && val instanceof (arrType);    //判断当前 值对象 类型是否和 arrType 一致
                arr = arrTypeSame ? val : (new arrType(Array.isArray(val) ? val : [val]));
            }
            gl.bufferSubData(gl.UNIFORM_BUFFER, offset, arr);
        }

        /**
         * 设置 UBO 数据
         * @param useSP 使用的 WebGLProgram
         * @param uboBlockIndex ubo 块索引
         * @param valueMaps uniform值map 列表
         * @returns 
         */
        public uboDataSet(useSP: WebGLProgram, uboBlockIndex: number, ...valueMaps: uniformValueMap[]) {
            const gl = this.webgl2;
            //
            //从 block Parameter 获取信息 自动填充参数 使用默认的 std140 对齐模式
            //获取 block 字段数
            const lbSubIdxList = gl.getActiveUniformBlockParameter(useSP, uboBlockIndex, gl.UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES) as Uint32Array;
            const valMaps: { [key: string]: any }[] = valueMaps;
            if (!valueMaps || valueMaps.length < 1) {
                console.error(`valueMaps is null of empty. `);
                return;
            }

            let alignOffset = 0;    //对齐偏移
            let byteOffset = 0;     //实际byte偏移
            for (let i = 0, len = lbSubIdxList.length; i < len; i++) {
                const lbSubIdx = lbSubIdxList[i];
                //获取unifrom 信息
                const unifromInfo = gl.getActiveUniform(useSP, lbSubIdx);
                if (!unifromInfo) continue;

                const isArray = unifromInfo.size > 1;   //当前字段是否是数组
                // let unifName = unifromInfo.name.substring(unifromInfo.name.lastIndexOf(".") + 1);
                let unifName = unifromInfo.name;
                if (isArray) unifName = unifName.substring(0, unifName.length - 3);
                const vMapLen = valMaps.length;
                let vMapIdx = 0;
                let valMap = valMaps[vMapIdx];
                let val = valMap[unifName];
                //遍历 替补map
                while (val == null && vMapIdx < vMapLen) {
                    valMap = valMaps[vMapIdx];
                    vMapIdx++;
                    val = valMap[unifName];
                }

                if (val == null) {
                    //null 处理
                    if (isArray) val = [];
                }

                const unifTypeInfo = this._unifUseInfoMap[unifromInfo.type];
                let byteSize: number;
                let alignSize: number;
                byteSize = unifTypeInfo.lenght * 4;

                if (!isArray) {
                    alignSize = unifTypeInfo.align * 4;

                    alignOffset = Math.ceil(byteOffset / alignSize) * alignSize;
                    //包装数据
                    this.uboSubDataSet(alignOffset, val, unifTypeInfo.arrType);
                    //
                    byteOffset = alignOffset + byteSize;
                } else if (Array.isArray(val)) {    //数组模式，值对象也必须是数组
                    //数组模式，对齐 只考虑 4 * 4 = 16
                    alignSize = 16;
                    //实际 数组元素的单个内存占用 , 大于 16 是矩阵类型
                    const _arrByteSize = (unifTypeInfo.lenght < 4 ? 4 : unifTypeInfo.lenght) * 4;

                    for (let j = 0, len1 = unifromInfo.size; j < len1; j++) {
                        alignOffset = Math.ceil(byteOffset / alignSize) * alignSize;

                        const _val = val[j];
                        if (_val != null) {
                            this.uboSubDataSet(alignOffset, _val, unifTypeInfo.arrType);
                        } else {
                            console.error(` ${unifName} value array index of ${j} is null. `);
                        }

                        byteOffset = alignOffset + _arrByteSize;
                    }
                } else {
                    console.error(` ${unifName} value is invalid , The array type field of the block element. The value entered must be an array object. `);
                }
            }
        }
    }
}